package com.xyzwps.paimon.furina.modules.sms.vcode

import com.xyzwps.paimon.furina.common.AppErrorCode.*
import com.xyzwps.paimon.furina.common.AppException
import com.xyzwps.paimon.furina.common.DateTimeUtil
import com.xyzwps.paimon.furina.common.value.Phone
import com.xyzwps.paimon.furina.common.value.Vcode

class VcodeManager(
    private val vcodeContextRepository: VcodeContextRepository,
    private val vcodeGapSeconds: Int, // 两次验证法发送的最小时间间隔
    private val vcodeExpireSeconds: Int, // 验证码过期时间
    private val contextHistoryLength: Int, // 一个上下文中最多可以发几条验证码
    private val vcodeMaxCheckTimes: Int, // 一个新验证码最多允许 check 多少次
    private val sendVcodeFn: (Phone, Vcode, VcodeScene) -> Unit // 真正发送验证码的函数
) {

    fun send(phone: Phone, vcode: Vcode, scene: VcodeScene) {
        val ctx = vcodeContextRepository.getContext(phone, scene)
        val history = ctx.history

        // 从未发送过
        if (history.isEmpty()) {
            val newCtx = ctx.copy(history = listOf(VcodeSendItem(vcode, DateTimeUtil.currentTsSeconds(), 0)))
            vcodeContextRepository.createContext(phone, scene, newCtx)
            return sendVcodeFn(phone, vcode, scene)
        }

        // 发送超过限制
        if (history.size >= contextHistoryLength) {
            throw AppException(VCODE_SEND_TOO_MANY, "验证码发送过多，请24小时后再试")
        }

        val latestSendItem = history.last()

        // 发送太频繁
        if (DateTimeUtil.currentTsSeconds() - latestSendItem.tsSeconds < vcodeGapSeconds) {
            val leftSeconds = vcodeGapSeconds - (DateTimeUtil.currentTsSeconds() - latestSendItem.tsSeconds)
            val payload = mapOf("leftSeconds" to leftSeconds)
            throw AppException(VCODE_SEND_ALREADY, "验证码已发送", payload)
        }

        // 终于可以发了
        val newHistory = buildList {
            addAll(history)
            add(VcodeSendItem(vcode, DateTimeUtil.currentTsSeconds(), 0))
        }
        val newCtx = ctx.copy(history = newHistory)
        vcodeContextRepository.saveContext(phone, scene, newCtx)
        return sendVcodeFn(phone, vcode, scene)
    }

    fun check(phone: Phone, vcode: Vcode, scene: VcodeScene) {
        val ctx = vcodeContextRepository.getContext(phone, scene)
        val history = ctx.history

        // 从未发过验证码，或者验证码上下文过期消失
        if (history.isEmpty()) {
            throw AppException(VCODE_NOT_SEND_OR_TIMEOUT, "请先发送验证码")
        }

        val latestSendItem = history.last()

        // 重试次数太多
        if (latestSendItem.checkTimes >= vcodeMaxCheckTimes) {
            throw AppException(VCODE_CANNOT_BE_USED, "请重新发送验证码")
        }

        // 过期
        if (DateTimeUtil.currentTsSeconds() - latestSendItem.tsSeconds >= vcodeExpireSeconds) {
            throw AppException(VCODE_CANNOT_BE_USED, "验证码已过期，请重新发送验证码")
        }

        // 验证码不正确
        if (vcode != latestSendItem.vcode) {
            val newHistory = buildList {
                addAll(history.take(history.size - 1))
                add(latestSendItem.copy(checkTimes = latestSendItem.checkTimes + 1))
            }
            val newCtx = ctx.copy(history = newHistory)
            vcodeContextRepository.saveContext(phone, scene, newCtx)
            throw AppException(VCODE_NOT_MATCH, "验证码不正确")
        }
    }
}



